/*
 *   Copyright (C) 2006  The Concord Consortium, Inc.,
 *   25 Love Lane, Concord, MA 01742
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * END LICENSE */

package org.concord.modeler.g2d;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Point;
import java.text.NumberFormat;
import java.util.Enumeration;
import java.util.Locale;
import java.util.Vector;

/**
 * This class controls the look and feel of axes. It is designed to be used in conjunction with the Graph2D class and
 * DataSet class for plotting 2D graphs.
 * 
 * To work with the other classes a system of registration is used. The axes have to be attached to the controlling
 * Graph2D class and the DataSet's have to be attached to both the Graph2D class and the Axis class.
 * 
 * This way the 3 main classes Graph2D, Axis and DataSet know of each others existence.
 * 
 * This does not mean the classes cannot be used independently, they can but in this mode nothing is automated, the user
 * must code everything manually
 * 
 * @version $Revision: 1.13 $, $Date: 2007-07-16 14:00:42 $.
 * @author Leigh Brookshaw
 */

public class Axis {

	/**
	 * Constant flagging Horizontal Axis
	 */
	public static final int HORIZONTAL = 0;

	/**
	 * Constant flagging Vertical Axis
	 */
	public static final int VERTICAL = 1;

	/**
	 * Constant flagging Axis position on the graph. Left side => Vertical
	 */
	public static final int LEFT = 2;
	/**
	 * Constant flagging Axis position on the graph. Right side => Vertical
	 */
	public static final int RIGHT = 3;
	/**
	 * Constant flagging Axis position on the graph. Top side => Horizontal
	 */
	public static final int TOP = 4;
	/**
	 * Constant flagging Axis position on the graph. Bottom side => Horizontal
	 */
	public static final int BOTTOM = 5;
	/**
	 * The first guess on the number of Labeled Major tick marks.
	 */
	static final int NUMBER_OF_TICS = 4;

	/**
	 * If <i>true</i> draw a grid positioned on major ticks over the graph
	 */
	public boolean drawgrid = false;
	/**
	 * If <i>true</i> draw a line positioned on the Zero label tick mark.
	 */
	public boolean drawzero = false;
	/**
	 * Color of the grid
	 */
	public Color gridcolor = null;
	/**
	 * Color of the line at the Zero label
	 */
	public Color zerocolor = null;
	/**
	 * Default value <i>true</i>. Normally never changed. If set <i>false</I> the Axis draw method exits without
	 * drawing the axis.
	 * 
	 * @see org.concord.modeler.g2d.Axis#drawAxis(java.awt.Graphics)
	 */
	public boolean redraw = true;
	/**
	 * Rescale the axis so that labels fall at the end of the Axis. Default value <i>false</i>.
	 */
	public boolean force_end_labels = false;
	/**
	 * Size in pixels of the major tick marks
	 */
	public int major_tic_size = 10;
	/**
	 * Size in pixels of the minor tick marks
	 */
	public int minor_tic_size = 5;
	/**
	 * Number of minor tick marks between major tick marks
	 */
	public int minor_tic_count = 1;
	/**
	 * Color of the Axis.
	 */
	public Color axiscolor;
	/**
	 * Minimum data value of the axis. This is the value used to scale data into the data window. This is the value to
	 * alter to force a rescaling of the data window.
	 */
	public double minimum;
	/**
	 * Maximum data value of the axis. This is the value used to scale data into the data window. This is the value to
	 * alter to force a rescaling of the data window.
	 */
	public double maximum;
	/**
	 * Before the Axis can be positioned correctly and drawn the data window needs to be calculated and passed to the
	 * Axis.
	 */
	public Dimension data_window = new Dimension(0, 0);

	/**
	 * The graph canvas this axis is attached to (if it is attached to any)
	 * 
	 * @see org.concord.modeler.g2d.Graph2D
	 */
	public Graph2D g2d = null;

	private CurveGroup curveGroup;

	/**
	 * The position in pixels of the minimum point of the axis line
	 */
	protected Point amin;

	/**
	 * The position in pixels of the maximum point of the axis line
	 */
	protected Point amax;

	/**
	 * The orientation of the axis. Either Axis.HORIZONTAL or Axis.VERTICAL
	 */
	protected int orientation;

	/**
	 * The position of the axis. Either Axis.LEFT, Axis.RIGHT, Axis.TOP, or Axis.BOTTOM
	 */
	protected int position;

	/**
	 * The width of the Axis. Where width for a horizontal axis is really the height
	 */
	protected int width = 0;

	/**
	 * Textline class to contain the title of the axis.
	 */
	protected RTextLine title = new RTextLine();

	/**
	 * Textline class to hold the labels before printing.
	 */
	protected RTextLine label = new RTextLine("0");
	/**
	 * Textline class to hold the label's exponent (if it has one).
	 */
	protected RTextLine exponent = new RTextLine();
	/**
	 * The width of the maximum label. Used to position a Vertical Axis.
	 */
	protected int max_label_width = 0;
	/**
	 * Vector containing a list of attached DataSets
	 */
	protected Vector<DataSet> dataset = new Vector<DataSet>();
	/**
	 * String to contain the labels.
	 */
	protected String label_string[] = null;
	/**
	 * The actual values of the axis labels
	 */
	protected float label_value[] = null;
	/**
	 * The starting value of the labels
	 */
	protected double label_start = 0.0;
	/**
	 * The increment between labels
	 */
	protected double label_step = 0.0;
	/**
	 * The label exponent
	 */
	protected int label_exponent = 0;
	/**
	 * The number of labels required
	 */
	protected int label_count = 0;
	/**
	 * Initial guess for the number of labels required
	 */
	protected int guess_label_number = 4;

	/**
	 * If true the axis range must be manually set by setting the Axis.minimum and Axis.maximum variables. The default
	 * is false. The default action is for the axis range to be calculated everytime a dataset is attached.
	 */
	protected boolean manualRange = false;

	protected NumberFormat formatter;

	/**
	 * Instantiate the class. The defalt type is a Horizontal axis positioned at the bottom of the graph.
	 */
	public Axis() {
		orientation = HORIZONTAL;
		position = BOTTOM;
		setFormat();
	}

	/**
	 * Instantiate the class. Setting the position.
	 * 
	 * @param p
	 *            Set the axis position. Must be one of Axis.BOTTOM, Axis.TOP, Axis.LEFT, Axis.RIGHT, Axis.HORIZONTAL or
	 *            Axis.VERTICAL. If one of the latter two are used then Axis.BOTTOM or Axis.LEFT is assumed.
	 */
	public Axis(int p) {

		setPosition(p);

		switch (position) {
		case LEFT:
		case VERTICAL:
			title.setRotation(90);
			break;
		case RIGHT:
			title.setRotation(-90);
			break;
		default:
			title.setRotation(0);
			break;
		}

		setFormat();

	}

	private void setFormat() {
		formatter = NumberFormat.getNumberInstance(Locale.US);
		formatter.setParseIntegerOnly(false);
		formatter.setMaximumFractionDigits(2);
		formatter.setMinimumFractionDigits(0);
	}

	/** set the curve group this axis serves for */
	public void setCurveGroup(CurveGroup cg) {
		curveGroup = cg;
	}

	/** get the curve group this axis serves for */
	public CurveGroup getCurveGroup() {
		return curveGroup;
	}

	public void addCurveGroup(CurveGroup cg) {
	}

	/**
	 * Set the axis position.
	 * 
	 * @param p
	 *            Must be one of Axis.BOTTOM, Axis.TOP, Axis.LEFT, Axis.RIGHT, Axis.HORIZONTAL or Axis.VERTICAL. If one
	 *            of the latter two are used then Axis.BOTTOM or Axis.LEFT is assumed.
	 */
	public void setPosition(int p) {
		position = p;

		switch (position) {
		case LEFT:
			orientation = VERTICAL;
			break;
		case RIGHT:
			orientation = VERTICAL;
			break;

		case TOP:
			orientation = HORIZONTAL;
			break;
		case BOTTOM:
			orientation = HORIZONTAL;
			break;
		case HORIZONTAL:
			orientation = HORIZONTAL;
			position = BOTTOM;
			break;
		case VERTICAL:
			orientation = VERTICAL;
			position = LEFT;
			break;
		default:
			orientation = HORIZONTAL;
			position = BOTTOM;
			break;
		}
	}

	/**
	 * Attach a DataSet for the Axis to manage.
	 * 
	 * @param d
	 *            dataSet to attach
	 * @see org.concord.modeler.g2d.DataSet
	 */
	public void attachDataSet(DataSet d) {
		if (orientation == HORIZONTAL)
			attachXdata(d);
		else attachYdata(d);
	}

	/**
	 * Detach an attached DataSet
	 * 
	 * @param d
	 *            dataSet to detach
	 * @see org.concord.modeler.g2d.DataSet
	 */
	public void detachDataSet(DataSet d) {

		if (d == null)
			return;

		if (orientation == HORIZONTAL) {
			d.xaxis = null;
		}
		else {
			d.yaxis = null;
		}
		dataset.removeElement(d);

		if (!manualRange)
			resetRange();
	}

	/**
	 * Detach All attached dataSets.
	 */
	public void detachAll() {
		int i;
		DataSet d;

		if (dataset.isEmpty())
			return;

		if (orientation == HORIZONTAL) {
			for (i = 0; i < dataset.size(); i++) {
				d = dataset.elementAt(i);
				d.xaxis = null;
			}
		}
		else {
			for (i = 0; i < dataset.size(); i++) {
				d = dataset.elementAt(i);
				d.yaxis = null;
			}
		}

		dataset.removeAllElements();

		minimum = 0.0;
		maximum = 0.0;
	}

	/**
	 * Return the minimum value of All datasets attached to the axis.
	 * 
	 * @return Data minimum
	 */
	public double getDataMin() {
		double m;
		Enumeration e;
		DataSet d;

		if (dataset.isEmpty())
			return 0.0;

		d = dataset.firstElement();
		if (d == null)
			return 0.0;

		if (orientation == HORIZONTAL) {
			m = d.getXmin();
			for (e = dataset.elements(); e.hasMoreElements();) {

				d = (DataSet) e.nextElement();
				m = Math.min(d.getXmin(), m);

			}
		}
		else {
			m = d.getYmin();
			for (e = dataset.elements(); e.hasMoreElements();) {

				d = (DataSet) e.nextElement();
				m = Math.min(d.getYmin(), m);

			}
		}

		return m;
	}

	/**
	 * Return the maximum value of All datasets attached to the axis.
	 * 
	 * @return Data maximum
	 */
	public double getDataMax() {
		double m;
		Enumeration e;
		DataSet d;

		if (dataset.isEmpty())
			return 0.0;

		d = dataset.firstElement();

		if (d == null)
			return 0.0;

		if (orientation == HORIZONTAL) {
			m = d.getXmax();
			for (e = dataset.elements(); e.hasMoreElements();) {

				d = (DataSet) e.nextElement();
				m = Math.max(d.getXmax(), m);
			}
		}
		else {
			m = d.getYmax();
			for (e = dataset.elements(); e.hasMoreElements();) {

				d = (DataSet) e.nextElement();

				m = Math.max(d.getYmax(), m);
			}
		}

		return m;
	}

	/**
	 * Return the pixel equivalent of the passed data value. Using the position of the axis and the maximum and minimum
	 * values convert the data value into a pixel value
	 * 
	 * @param v
	 *            data value to convert
	 * @return equivalent pixel value
	 * @see org.concord.modeler.g2d.Axis#getDouble(int)
	 */
	public int getInteger(double v) {
		if (amin == null || amax == null)
			return (int) v;
		double scale;
		if (orientation == HORIZONTAL) {
			scale = (amax.x - amin.x) / (maximum - minimum);
			return amin.x + (int) ((v - minimum) * scale);
		}
		scale = (amax.y - amin.y) / (maximum - minimum);
		return amin.y + (int) ((maximum - v) * scale);
	}

	/**
	 * Return the data value equivalent of the passed pixel position. Using the position of the axis and the maximum and
	 * minimum values convert the pixel position into a data value
	 * 
	 * @param i
	 *            pixel value
	 * @return equivalent data value
	 * @see org.concord.modeler.g2d.Axis#getInteger(double)
	 */
	public double getDouble(int i) {
		double scale;

		if (orientation == HORIZONTAL) {
			scale = (maximum - minimum) / (amax.x - amin.x);
			return minimum + (i - amin.x) * scale;
		}
		scale = (maximum - minimum) / (amax.y - amin.y);
		return maximum - (i - amin.y) * scale;
	}

	/**
	 * Reset the range of the axis (the minimum and maximum values) to the default data values.
	 */
	public void resetRange() {
		minimum = getDataMin();
		maximum = getDataMax();
	}

	/**
	 * Return the position of the Axis.
	 * 
	 * @return One of Axis.LEFT, Axis.RIGHT, Axis.TOP, or Axis.BOTTOM.
	 */
	public int getAxisPos() {
		return position;
	}

	/**
	 * If the Axis is Vertical return <i>true</i>.
	 */
	public boolean isVertical() {
		if (orientation == HORIZONTAL)
			return false;
		return true;
	}

	/**
	 * Return the width of the axis.
	 * 
	 * @param g
	 *            graphics context.
	 */
	public int getAxisWidth(Graphics g) {
		int i;
		width = 0;

		if (minimum == maximum)
			return 0;
		if (dataset.size() == 0)
			return 0;

		calculateGridLabels();

		exponent.setText(null);

		if (label_exponent != 0) {
			exponent.copyState(label);
			exponent.setText("x10^" + String.valueOf(label_exponent));
		}

		if (orientation == HORIZONTAL) {

			width = label.getRHeight(g) + label.getLeading(g);
			width += Math.max(title.getRHeight(g), exponent.getRHeight(g));

		}
		else {
			for (i = 0; i < label_string.length; i++) {
				label.setText(" " + label_string[i]);
				width = Math.max(label.getRWidth(g), width);
			}
			max_label_width = width;
			width = 0;

			if (!title.isNull()) {
				width = Math.max(width, title.getRWidth(g) + title.charWidth(g, ' '));
			}

			if (!exponent.isNull()) {
				width = Math.max(width, exponent.getRWidth(g) + exponent.charWidth(g, ' '));
			}
			width += max_label_width;
		}

		return width;
	}

	/**
	 * Position the axis at the passed coordinates. The coordinates should match the type of axis.
	 * 
	 * @param xmin
	 *            The minimum X pixel
	 * @param xmax
	 *            The maximum X pixel. These should be equal if the axis is vertical
	 * @param ymin
	 *            The minimum Y pixel
	 * @param ymax
	 *            The maximum Y pixel. These should be equal if the axis is horizontal
	 * @return <i>true</i> if there are no inconsistencies.
	 */
	public boolean positionAxis(int xmin, int xmax, int ymin, int ymax) {
		if (orientation == HORIZONTAL && ymin != ymax)
			return false;
		if (orientation == VERTICAL && xmin != xmax)
			return false;
		if (amin == null)
			amin = new Point(xmin, ymin);
		else {
			amin.x = xmin;
			amin.y = ymin;
		}
		if (amax == null)
			amax = new Point(xmax, ymax);
		else {
			amax.x = xmax;
			amax.y = ymax;
		}
		return true;
	}

	/**
	 * Draw the axis using the passed Graphics context.
	 * 
	 * @param g
	 *            Graphics context for drawing
	 */
	public void drawAxis(Graphics g) {
		Graphics lg;

		if (!redraw)
			return;

		if (minimum == maximum) {
			resetRange();
			if (minimum == maximum) {
				minimum -= 0.5;
				maximum += 0.5;
			}
		}
		// if( amin.equals(amax) ) return;
		if (width == 0)
			width = getAxisWidth(g);

		lg = g.create();

		if (force_end_labels) {
			minimum = label_start;
			maximum = minimum + (label_count - 1) * label_step;
		}

		/*
		 * For rotated text set the Component that is being drawn into
		 */
		title.setDrawingComponent(g2d);
		label.setDrawingComponent(g2d);
		exponent.setDrawingComponent(g2d);

		if (orientation == HORIZONTAL) {
			drawHAxis(lg);
		}
		else {
			drawVAxis(lg);
		}

	}

	/**
	 * Set the title of the axis
	 * 
	 * @param s
	 *            string containing text.
	 */
	public void setTitleText(String s) {
		title.setText(s);
	}

	public String getTitleText() {
		return title.getText();
	}

	/**
	 * Set the color of the title
	 * 
	 * @param c
	 *            Color of the title.
	 */
	public void setTitleColor(Color c) {
		title.setColor(c);
	}

	/**
	 * Set the font of the title
	 * 
	 * @param c
	 *            Title font.
	 */
	public void setTitleFont(Font f) {
		title.setFont(f);
		if (curveGroup != null) {
			curveGroup.getLabelOfX().setFont(f);
			curveGroup.getLabelOfY().setFont(f);
		}
	}

	public Font getTitleFont() {
		return title.getFont();
	}

	/**
	 * Set the title rotation angle. Only multiples of 90 degrees allowed.
	 * 
	 * @param a
	 *            Title rotation angle in degrees.
	 */
	public void setTitleRotation(int a) {
		title.setRotation(a);
	}

	/**
	 * Set the color of the labels
	 * 
	 * @param c
	 *            Color of the labels.
	 */
	public void setLabelColor(Color c) {
		label.setColor(c);
	}

	/**
	 * Set the font of the labels.
	 * 
	 * @param f
	 *            font.
	 */
	public void setLabelFont(Font f) {
		label.setFont(f);
	}

	/**
	 * Set the color of the exponent
	 * 
	 * @param c
	 *            Color.
	 */
	public void setExponentColor(Color c) {
		exponent.setColor(c);
	}

	/**
	 * Set the font of the exponent
	 * 
	 * @param f
	 *            font.
	 */
	public void setExponentFont(Font f) {
		exponent.setFont(f);
	}

	/**
	 * Is the range of the axis to be set automatically (based on the data) or manually by setting the values
	 * Axis.minimum and Axis.maximum?
	 * 
	 * @param b
	 *            boolean value.
	 */
	public void setManualRange(boolean b) {
		manualRange = b;
	}

	/**
	 * Draw a Horizontal Axis.
	 * 
	 * @param g
	 *            Graphics context.
	 */

	protected void drawHAxis(Graphics g) {
		int i;
		int j;
		int x0, y0, x1, y1;
		int direction;
		int offset;
		double minor_step;

		Color c;

		double vmin = minimum * 1.001;
		double vmax = maximum * 1.001;

		double scale = (amax.x - amin.x) / (maximum - minimum);
		double val;
		double minor;

		if (axiscolor != null)
			g.setColor(axiscolor);

		g.drawLine(amin.x, amin.y, amax.x, amax.y);

		if (position == TOP)
			direction = 1;
		else direction = -1;

		minor_step = label_step / (minor_tic_count + 1);
		val = label_start;
		for (i = 0; i < label_count; i++) {
			if (val >= vmin && val <= vmax) {
				y0 = amin.y;
				x0 = amin.x + (int) ((val - minimum) * scale);
				if (Math.abs(label_value[i]) <= 0.0001 && drawzero) {
					c = g.getColor();
					if (zerocolor != null)
						g.setColor(zerocolor);
					g.drawLine(x0, y0, x0, y0 + data_window.height * direction);
					g.setColor(c);

				}
				else if (drawgrid) {
					c = g.getColor();
					if (gridcolor != null)
						g.setColor(gridcolor);
					g.drawLine(x0, y0, x0, y0 + data_window.height * direction);
					g.setColor(c);
				}
				x1 = x0;
				y1 = y0 + major_tic_size * direction;
				g.drawLine(x0, y0, x1, y1);
			}

			minor = val + minor_step;
			for (j = 0; j < minor_tic_count; j++) {
				if (minor >= vmin && minor <= vmax) {
					y0 = amin.y;
					x0 = amin.x + (int) ((minor - minimum) * scale);
					if (drawgrid) {
						c = g.getColor();
						if (gridcolor != null)
							g.setColor(gridcolor);
						g.drawLine(x0, y0, x0, y0 + data_window.height * direction);
						g.setColor(c);
					}
					x1 = x0;
					y1 = y0 + minor_tic_size * direction;
					g.drawLine(x0, y0, x1, y1);
				}
				minor += minor_step;
			}

			val += label_step;
		}

		if (position == TOP) {
			offset = -label.getLeading(g) - label.getDescent(g);
		}
		else {
			offset = +label.getLeading(g) + label.getAscent(g);
		}

		val = label_start;
		for (i = 0; i < label_count; i++) {
			if (val >= vmin && val <= vmax) {
				y0 = amin.y + offset;
				x0 = amin.x + (int) ((val - minimum) * scale);
				label.setText(label_string[i]);
				label.draw(g, x0, y0, TextLine.CENTER);
			}
			val += label_step;
		}

		if (!exponent.isNull()) {
			if (position == TOP) {
				y0 = amin.y - label.getLeading(g) - label.getDescent(g) - exponent.getLeading(g)
						- exponent.getDescent(g);
			}
			else {
				y0 = amax.y + label.getLeading(g) + label.getAscent(g) + exponent.getLeading(g) + exponent.getAscent(g);
			}

			// x0 = amax.x; //Charles: exponent not seen
			x0 = amax.x - 10;

			exponent.draw(g, x0, y0, TextLine.LEFT);

		}

		if (!title.isNull()) {
			if (position == TOP) {
				y0 = amin.y - label.getLeading(g) - label.getDescent(g) - title.getLeading(g) - title.getDescent(g);
			}
			else {
				y0 = amax.y + label.getLeading(g) + label.getAscent(g) + title.getLeading(g) + title.getAscent(g);
			}

			x0 = amin.x + (amax.x - amin.x) / 2;

			title.draw(g, x0, y0, TextLine.CENTER);

		}

	}

	/**
	 * Draw a Vertical Axis.
	 * 
	 * @param g
	 *            Graphics context.
	 */

	protected void drawVAxis(Graphics g) {
		int i;
		int j;
		int x0, y0, x1, y1;
		int direction;
		int offset = 0;
		double minor_step;
		double minor;
		Color c;

		double vmin = minimum * 1.001;
		double vmax = maximum * 1.001;

		double scale = (amax.y - amin.y) / (maximum - minimum);
		double val;

		if (axiscolor != null)
			g.setColor(axiscolor);

		g.drawLine(amin.x, amin.y, amax.x, amax.y);

		if (position == RIGHT)
			direction = -1;
		else direction = 1;

		minor_step = label_step / (minor_tic_count + 1);
		val = label_start;
		for (i = 0; i < label_count; i++) {
			if (val >= vmin && val <= vmax) {
				x0 = amin.x;
				y0 = amax.y - (int) ((val - minimum) * scale);
				if (Math.abs(label_value[i]) <= 0.0001 && drawzero) {
					c = g.getColor();
					if (zerocolor != null)
						g.setColor(zerocolor);
					g.drawLine(x0, y0, x0 + data_window.width * direction, y0);
					g.setColor(c);
				}
				else if (drawgrid) {
					c = g.getColor();
					if (gridcolor != null)
						g.setColor(gridcolor);
					g.drawLine(x0, y0, x0 + data_window.width * direction, y0);
					g.setColor(c);
				}
				x1 = x0 + major_tic_size * direction;
				y1 = y0;
				g.drawLine(x0, y0, x1, y1);
			}

			minor = val + minor_step;
			for (j = 0; j < minor_tic_count; j++) {
				if (minor >= vmin && minor <= vmax) {
					x0 = amin.x;
					y0 = amax.y - (int) ((minor - minimum) * scale);
					if (drawgrid) {
						c = g.getColor();
						if (gridcolor != null)
							g.setColor(gridcolor);
						g.drawLine(x0, y0, x0 + data_window.width * direction, y0);
						g.setColor(c);
					}
					x1 = x0 + minor_tic_size * direction;
					y1 = y0;
					g.drawLine(x0, y0, x1, y1);
				}
				minor += minor_step;
			}
			val += label_step;
		}

		val = label_start;
		for (i = 0; i < label_count; i++) {
			if (val >= vmin && val <= vmax) {
				x0 = amin.x + offset;
				y0 = amax.y - (int) ((val - minimum) * scale) + label.getAscent(g) / 2;

				if (position == RIGHT) {
					label.setText(" " + label_string[i]);
					label.draw(g, x0, y0, TextLine.LEFT);
				}
				else {
					label.setText(label_string[i] + " ");
					label.draw(g, x0, y0, TextLine.RIGHT);
				}
			}
			val += label_step;
		}

		if (!exponent.isNull()) {

			y0 = amin.y;

			if (position == RIGHT) {
				x0 = amin.x + max_label_width + exponent.charWidth(g, ' ');
				exponent.draw(g, x0, y0, TextLine.LEFT);
			}
			else {
				x0 = amin.x - max_label_width - exponent.charWidth(g, ' ');
				exponent.draw(g, x0, y0, TextLine.RIGHT);
			}

		}

		if (!title.isNull()) {

			y0 = amin.y + (amax.y - amin.y) / 2;

			if (title.getRotation() == 0 || title.getRotation() == 180) {
				if (position == RIGHT) {
					x0 = amin.x + max_label_width + title.charWidth(g, ' ');
					title.draw(g, x0, y0, TextLine.LEFT);
				}
				else {
					x0 = amin.x - max_label_width - title.charWidth(g, ' ');
					title.draw(g, x0, y0, TextLine.RIGHT);
				}
			}
			else {
				title.setJustification(TextLine.CENTER);
				if (position == RIGHT) {
					x0 = amin.x + max_label_width - title.getLeftEdge(g) + +title.charWidth(g, ' ');
				}
				else {
					x0 = amin.x - max_label_width - title.getRightEdge(g) - title.charWidth(g, ' ');
				}
				title.draw(g, x0, y0);
			}

		}

	}

	/**
	 * Attach a DataSet to a Horizontal Axis
	 * 
	 * @param d
	 *            dataset to attach.
	 */
	protected void attachXdata(DataSet d) {

		dataset.addElement(d);
		d.xaxis = this;

		if (dataset.size() == 1) {
			minimum = d.dxmin;
			maximum = d.dxmax;
		}
		else {
			if (minimum > d.dxmin)
				minimum = d.dxmin;
			if (maximum < d.dxmax)
				maximum = d.dxmax;
		}

	}

	/**
	 * Attach a DataSet to a Vertical Axis
	 * 
	 * @param d
	 *            dataset to attach.
	 */
	protected void attachYdata(DataSet d) {

		dataset.addElement(d);
		d.yaxis = this;

		if (dataset.size() == 1) {
			minimum = d.dymin;
			maximum = d.dymax;
		}
		else {
			if (minimum > d.dymin)
				minimum = d.dymin;
			if (maximum < d.dymax)
				maximum = d.dymax;
		}

	}

	/**
	 * calculate the labels
	 */
	protected void calculateGridLabels() {
		double val;
		int i;
		int j;

		if (Math.abs(minimum) > Math.abs(maximum))
			label_exponent = ((int) Math.floor(SpecialFunction.log10(Math.abs(minimum)) / 3.0)) * 3;
		else label_exponent = ((int) Math.floor(SpecialFunction.log10(Math.abs(maximum)) / 3.0)) * 3;

		label_step = RoundUp((maximum - minimum) / guess_label_number);
		label_start = Math.floor(minimum / label_step) * label_step;

		val = label_start;
		label_count = 1;
		while (val < maximum) {
			val += label_step;
			label_count++;
		}

		label_string = new String[label_count];
		label_value = new float[label_count];

		for (i = 0; i < label_count; i++) {
			val = label_start + i * label_step;

			if (label_exponent < 0) {
				for (j = label_exponent; j < 0; j++) {
					val *= 10;
				}
			}
			else {
				for (j = 0; j < label_exponent; j++) {
					val /= 10;
				}
			}

			label_string[i] = formatter.format(val);
			label_value[i] = (float) val;
		}

	}

	/**
	 * Round up the passed value to a NICE value.
	 */

	private double RoundUp(double val) {
		int exponent;
		int i;

		exponent = (int) (Math.floor(SpecialFunction.log10(val)));

		if (exponent < 0) {
			for (i = exponent; i < 0; i++) {
				val *= 10.0;
			}
		}
		else {
			for (i = 0; i < exponent; i++) {
				val /= 10.0;
			}
		}

		if (val > 5.0)
			val = 10.0;
		else if (val > 2.0)
			val = 5.0;
		else if (val > 1.0)
			val = 2.0;
		else val = 1.0;

		if (exponent < 0) {
			for (i = exponent; i < 0; i++) {
				val /= 10.0;
			}
		}
		else {
			for (i = 0; i < exponent; i++) {
				val *= 10.0;
			}
		}

		return val;

	}

}
